<!--
 * @Description: 自定义图片预览组件 - 提供图片点击预览功能
 * @Author: lujq
 * @Date: 2025-01-06
 * @LastEditors: lujq
 * @LastEditTime: 2025-01-06 18:30:00
-->
<template>
  <div class="image-preview-wrapper">
    <!-- 缩略图 -->
    <div class="thumbnail" @click="openPreview">
      <el-image :src="src" fit="cover" :style="thumbnailStyle" :class="thumbnailClass">
        <template #error>
          <div class="image-slot">
            <el-icon><Picture /></el-icon>
          </div>
        </template>
      </el-image>
    </div>

    <!-- 预览遮罩层 - 使用Teleport挂载到body -->
    <Teleport to="body">
      <div v-if="visible" class="preview-overlay" @click="closePreview">
        <div class="preview-container" @click.stop>
          <!-- 关闭按钮 -->
          <div class="close-btn" @click="closePreview">
            <el-icon><Close /></el-icon>
          </div>

          <!-- 预览图片 -->
          <div class="preview-image-wrapper">
            <el-image
              :src="src"
              fit="contain"
              class="preview-image"
              :style="{ transform: `scale(${scale}) rotate(${rotation}deg)` }"
            >
              <template #error>
                <div class="preview-error">
                  <el-icon><Picture /></el-icon>
                  <p>图片加载失败</p>
                </div>
              </template>
            </el-image>
          </div>

          <!-- 操作工具栏 -->
          <div class="toolbar">
            <div class="toolbar-item" @click="zoomOut" :class="{ disabled: scale <= 0.2 }">
              <el-icon><ZoomOut /></el-icon>
            </div>
            <div class="toolbar-item" @click="zoomIn" :class="{ disabled: scale >= 3 }">
              <el-icon><ZoomIn /></el-icon>
            </div>
            <div class="toolbar-item" @click="resetZoom">
              <el-icon><Refresh /></el-icon>
            </div>
            <div class="toolbar-item" @click="rotateLeft">
              <el-icon><RefreshLeft /></el-icon>
            </div>
            <div class="toolbar-item" @click="rotateRight">
              <el-icon><RefreshRight /></el-icon>
            </div>
          </div>

          <!-- 图片信息 -->
          <div v-if="showInfo" class="image-info">
            <p>{{ title || '图片预览' }}</p>
            <p class="zoom-info">缩放: {{ Math.round(scale * 100) }}%</p>
          </div>
        </div>
      </div>
    </Teleport>
  </div>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue'
import {
  Picture,
  Close,
  ZoomOut,
  ZoomIn,
  Refresh,
  RefreshLeft,
  RefreshRight,
} from '@element-plus/icons-vue'

/**
 * 组件属性定义
 */
interface Props {
  /** 图片地址 */
  src: string
  /** 图片标题 */
  title?: string
  /** 缩略图宽度 */
  width?: string | number
  /** 缩略图高度 */
  height?: string | number
  /** 缩略图圆角 */
  borderRadius?: string
  /** 缩略图自定义类名 */
  thumbnailClass?: string
  /** 是否显示图片信息 */
  showInfo?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  title: '',
  width: '50px',
  height: '50px',
  borderRadius: '4px',
  thumbnailClass: '',
  showInfo: true,
})

/**
 * 组件事件定义
 */
interface Emits {
  /** 预览打开事件 */
  open: []
  /** 预览关闭事件 */
  close: []
}

const emit = defineEmits<Emits>()

// 预览状态
const visible = ref(false)
const scale = ref(1)
const rotation = ref(0)

/**
 * 缩略图样式
 */
const thumbnailStyle = computed(() => ({
  width: typeof props.width === 'number' ? `${props.width}px` : props.width,
  height: typeof props.height === 'number' ? `${props.height}px` : props.height,
  borderRadius: props.borderRadius,
  cursor: 'pointer',
}))

/**
 * 打开预览
 */
const openPreview = () => {
  visible.value = true
  resetTransform()
  emit('open')
  // 阻止页面滚动
  document.body.style.overflow = 'hidden'
}

/**
 * 关闭预览
 */
const closePreview = () => {
  visible.value = false
  emit('close')
  // 恢复页面滚动
  document.body.style.overflow = ''
}

/**
 * 放大
 */
const zoomIn = () => {
  if (scale.value < 3) {
    scale.value = Math.min(3, scale.value + 0.2)
  }
}

/**
 * 缩小
 */
const zoomOut = () => {
  if (scale.value > 0.2) {
    scale.value = Math.max(0.2, scale.value - 0.2)
  }
}

/**
 * 重置缩放
 */
const resetZoom = () => {
  resetTransform()
}

/**
 * 向左旋转
 */
const rotateLeft = () => {
  rotation.value -= 90
}

/**
 * 向右旋转
 */
const rotateRight = () => {
  rotation.value += 90
}

/**
 * 重置变换
 */
const resetTransform = () => {
  scale.value = 1
  rotation.value = 0
}

/**
 * 键盘事件处理
 */
const handleKeydown = (e: KeyboardEvent) => {
  if (!visible.value) return

  switch (e.key) {
    case 'Escape':
      closePreview()
      break
    case '+':
    case '=':
      e.preventDefault()
      zoomIn()
      break
    case '-':
      e.preventDefault()
      zoomOut()
      break
    case '0':
      e.preventDefault()
      resetZoom()
      break
    case 'ArrowLeft':
      e.preventDefault()
      rotateLeft()
      break
    case 'ArrowRight':
      e.preventDefault()
      rotateRight()
      break
  }
}

// 监听键盘事件
document.addEventListener('keydown', handleKeydown)
</script>

<style scoped>
.image-preview-wrapper {
  display: inline-block;
}

.thumbnail {
  display: inline-block;
  transition: transform 0.2s ease;
}

.thumbnail:hover {
  transform: scale(1.05);
}

.image-slot {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100%;
  background: #f5f7fa;
  color: #909399;
  font-size: 14px;
}

.preview-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.8);
  z-index: 9999;
  display: flex;
  justify-content: center;
  align-items: center;
  backdrop-filter: blur(2px);
}

.preview-container {
  position: relative;
  width: 90vw;
  height: 90vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.close-btn {
  position: absolute;
  top: 20px;
  right: 20px;
  width: 40px;
  height: 40px;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 50%;
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: pointer;
  color: white;
  font-size: 18px;
  transition: background-color 0.2s ease;
  z-index: 10;
}

.close-btn:hover {
  background: rgba(255, 255, 255, 0.2);
}

.preview-image-wrapper {
  flex: 1;
  display: flex;
  justify-content: center;
  align-items: center;
  overflow: hidden;
  width: 100%;
}

.preview-image {
  max-width: 100%;
  max-height: 100%;
  transition: transform 0.3s ease;
  user-select: none;
}

.preview-error {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  color: #909399;
  font-size: 16px;
}

.preview-error .el-icon {
  font-size: 48px;
  margin-bottom: 12px;
}

.toolbar {
  display: flex;
  gap: 12px;
  padding: 20px;
  background: rgba(0, 0, 0, 0.5);
  border-radius: 8px;
  margin-top: 20px;
}

.toolbar-item {
  width: 40px;
  height: 40px;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 6px;
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: pointer;
  color: white;
  font-size: 16px;
  transition: all 0.2s ease;
}

.toolbar-item:hover:not(.disabled) {
  background: rgba(255, 255, 255, 0.2);
  transform: scale(1.1);
}

.toolbar-item.disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.image-info {
  position: absolute;
  bottom: 20px;
  left: 20px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 12px 16px;
  border-radius: 6px;
  font-size: 14px;
}

.image-info p {
  margin: 0;
  line-height: 1.4;
}

.zoom-info {
  opacity: 0.8;
  font-size: 12px;
  margin-top: 4px;
}

/* 响应式设计 */
@media (max-width: 768px) {
  .preview-container {
    width: 95vw;
    height: 95vh;
  }

  .close-btn {
    top: 10px;
    right: 10px;
    width: 36px;
    height: 36px;
    font-size: 16px;
  }

  .toolbar {
    gap: 8px;
    padding: 16px;
  }

  .toolbar-item {
    width: 36px;
    height: 36px;
    font-size: 14px;
  }

  .image-info {
    bottom: 10px;
    left: 10px;
    padding: 8px 12px;
    font-size: 12px;
  }
}
</style>
